/*-------------------------------------------------------------------------
 *
 * detoast.c
 *	  检索压缩或外部可变大小属性。
 *
 * Copyright (c) 2000-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/access/common/detoast.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/detoast.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/toast_internals.h"
#include "common/int.h"
#include "common/pg_lzcompress.h"
#include "utils/expandeddatum.h"
#include "utils/rel.h"

static struct varlena *fc_toast_fetch_datum(struct varlena *fc_attr);
static struct varlena *fc_toast_fetch_datum_slice(struct varlena *fc_attr,
											   int32 fc_sliceoffset,
											   int32 fc_slicelength);
static struct varlena *fc_toast_decompress_datum(struct varlena *fc_attr);
static struct varlena *fc_toast_decompress_datum_slice(struct varlena *fc_attr, int32 fc_slicelength);

/* ----------
 * detoast_external_attr -
 *
 *	公用入口点，用于从外部源（可能仍处于压缩格式）获取回一个吐司值。
 *
 * 这将返回一个包含所有数据的datum，即不依赖于外部存储或内存，但它仍然可以被压缩或具有短头。注意一些调用者假设如果输入是EXTERNAL datum，结果将是可以pfree的块。
 * ----------
 */
struct varlena * detoast_external_attr(struct varlena *fc_attr)
{
	struct varlena *fc_result;

	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		/*
		 * 这是一个外部存储的普通值
		 */
		fc_result = fc_toast_fetch_datum(fc_attr);
	}
	else if (VARATT_IS_EXTERNAL_INDIRECT(fc_attr))
	{
		/*
		 * 这是一个间接指针 --- 解引用它
		 */
		struct varatt_indirect fc_redirect;

		VARATT_EXTERNAL_GET_POINTER(fc_redirect, fc_attr);
		fc_attr = (struct varlena *) fc_redirect.pointer;

		/* 不允许嵌套间接Datum */
		Assert(!VARATT_IS_EXTERNAL_INDIRECT(fc_attr));

		/* 如果值仍以某种其他方式外部存在，则递归 */
		if (VARATT_IS_EXTERNAL(fc_attr))
			return detoast_external_attr(fc_attr);

		/*
		 * 复制到调用者的内存上下文中，以防调用者尝试
		 * pfree结果。
		 */
		fc_result = (struct varlena *) palloc(VARSIZE_ANY(fc_attr));
		memcpy(fc_result, fc_attr, VARSIZE_ANY(fc_attr));
	}
	else if (VARATT_IS_EXTERNAL_EXPANDED(fc_attr))
	{
		/*
		 * 这是一个扩展对象指针 --- 获取平面格式
		 */
		ExpandedObjectHeader *fc_eoh;
		Size		fc_resultsize;

		fc_eoh = DatumGetEOHP(PointerGetDatum(fc_attr));
		fc_resultsize = EOH_get_flat_size(fc_eoh);
		fc_result = (struct varlena *) palloc(fc_resultsize);
		EOH_flatten_into(fc_eoh, (void *) fc_result, fc_resultsize);
	}
	else
	{
		/*
		 * 这是主元组内部的一个普通值 - 为什么我被调用？
		 */
		fc_result = fc_attr;
	}

	return fc_result;
}


/* ----------
 * detoast_attr -
 *
 *	公用入口点，用于从压缩或外部存储中获取回一个吐司值。结果总是非扩展的varlena格式。
 *
 * 注意一些调用者假设如果输入是EXTERNAL或COMPRESSED datum，结果将是可以pfree的块。
 * ----------
 */
struct varlena * detoast_attr(struct varlena *fc_attr)
{
	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		/*
		 * 这是一个外部存储的datum --- 从那里获取它
		 */
		fc_attr = fc_toast_fetch_datum(fc_attr);
		/* 如果它被压缩，解压缩它 */
		if (VARATT_IS_COMPRESSED(fc_attr))
		{
			struct varlena *fc_tmp = fc_attr;

			fc_attr = fc_toast_decompress_datum(fc_tmp);
			pfree(fc_tmp);
		}
	}
	else if (VARATT_IS_EXTERNAL_INDIRECT(fc_attr))
	{
		/*
		 * 这是一个间接指针 --- 解引用它
		 */
		struct varatt_indirect fc_redirect;

		VARATT_EXTERNAL_GET_POINTER(fc_redirect, fc_attr);
		fc_attr = (struct varlena *) fc_redirect.pointer;

		/* 不允许嵌套间接Datum */
		Assert(!VARATT_IS_EXTERNAL_INDIRECT(fc_attr));

		/* 递归以防值以某种其他方式仍然扩展 */
		fc_attr = detoast_attr(fc_attr);

		/* 如果没有，我们最好复制它 */
		if (fc_attr == (struct varlena *) fc_redirect.pointer)
		{
			struct varlena *fc_result;

			fc_result = (struct varlena *) palloc(VARSIZE_ANY(fc_attr));
			memcpy(fc_result, fc_attr, VARSIZE_ANY(fc_attr));
			fc_attr = fc_result;
		}
	}
	else if (VARATT_IS_EXTERNAL_EXPANDED(fc_attr))
	{
		/*
		 * 这是一个扩展对象指针 --- 获取平面格式
		 */
		fc_attr = detoast_external_attr(fc_attr);
		/* 扁平化器不允许生成压缩/短输出 */
		Assert(!VARATT_IS_EXTENDED(fc_attr));
	}
	else if (VARATT_IS_COMPRESSED(fc_attr))
	{
		/*
		 * 这是主元组内部的一个压缩值
		 */
		fc_attr = fc_toast_decompress_datum(fc_attr);
	}
	else if (VARATT_IS_SHORT(fc_attr))
	{
		/*
		 * 这是一个短头varlena --- 转换为4字节头格式
		 */
		Size		fc_data_size = VARSIZE_SHORT(fc_attr) - VARHDRSZ_SHORT;
		Size		fc_new_size = fc_data_size + VARHDRSZ;
		struct varlena *fc_new_attr;

		fc_new_attr = (struct varlena *) palloc(fc_new_size);
		SET_VARSIZE(fc_new_attr, fc_new_size);
		memcpy(VARDATA(fc_new_attr), VARDATA_SHORT(fc_attr), fc_data_size);
		fc_attr = fc_new_attr;
	}

	return fc_attr;
}


/* ----------
 * detoast_attr_slice -
 *
 *	公用入口点，用于从压缩或外部存储中获取回一个吐司值的部分。
 *
 * sliceoffset是开始的位置（零或更多）
 * 如果slicelength < 0，返回sliceoffset之后的所有内容
 * ----------
 */
struct varlena * detoast_attr_slice(struct varlena *fc_attr,
				   int32 fc_sliceoffset, int32 fc_slicelength)
{
	struct varlena *fc_preslice;
	struct varlena *fc_result;
	char	   *fc_attrdata;
	int32		fc_slicelimit;
	int32		fc_attrsize;

	if (fc_sliceoffset < 0)
		elog(ERROR, "invalid sliceoffset: %d", fc_sliceoffset);

	/*
	 * 计算slicelimit = offset + length，或者如果必须获取全部值则为-1。
	 * 在整数溢出的情况下，我们必须获取全部。
	 */
	if (fc_slicelength < 0)
		fc_slicelimit = -1;
	else if (pg_add_s32_overflow(fc_sliceoffset, fc_slicelength, &fc_slicelimit))
		fc_slicelength = fc_slicelimit = -1;

	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		struct varatt_external fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

		/* 非压缩外部datum的快速路径 */
		if (!VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer))
			return fc_toast_fetch_datum_slice(fc_attr, fc_sliceoffset, fc_slicelength);

		/*
		 * 对于压缩值，我们需要获取足够的切片以解压缩
		 * 至少请求的部分（当请求前缀时）。
		 * 否则，直接获取所有切片。
		 */
		if (fc_slicelimit >= 0)
		{
			int32		fc_max_size = VARATT_EXTERNAL_GET_EXTSIZE(fc_toast_pointer);

			/*
			 * 确定指定长度的前缀所需的最大压缩数据量（解压缩后）。
			 *
			 * 至少现在，如果是LZ4数据，我们需要获取整个数据，因为
			 * 似乎没有API调用来确定我们需要多少压缩数据，以确保能够
			 * 解压缩所需的切片。
			 */
			if (VARATT_EXTERNAL_GET_COMPRESS_METHOD(fc_toast_pointer) ==
				TOAST_PGLZ_COMPRESSION_ID)
				fc_max_size = pglz_maximum_compressed_size(fc_slicelimit, fc_max_size);

			/*
			 * 提取足够的压缩切片（压缩标记将自动设置）。
			 */
			fc_preslice = fc_toast_fetch_datum_slice(fc_attr, 0, fc_max_size);
		}
		else
			fc_preslice = fc_toast_fetch_datum(fc_attr);
	}
	else if (VARATT_IS_EXTERNAL_INDIRECT(fc_attr))
	{
		struct varatt_indirect fc_redirect;

		VARATT_EXTERNAL_GET_POINTER(fc_redirect, fc_attr);

		/* 不允许嵌套间接Datum */
		Assert(!VARATT_IS_EXTERNAL_INDIRECT(fc_redirect.pointer));

		return detoast_attr_slice(fc_redirect.pointer,
								  fc_sliceoffset, fc_slicelength);
	}
	else if (VARATT_IS_EXTERNAL_EXPANDED(fc_attr))
	{
		/* 交给detoast_external_attr来扁平化 */
		fc_preslice = detoast_external_attr(fc_attr);
	}
	else
		fc_preslice = fc_attr;

	Assert(!VARATT_IS_EXTERNAL(fc_preslice));

	if (VARATT_IS_COMPRESSED(fc_preslice))
	{
		struct varlena *fc_tmp = fc_preslice;

		/* 解压缩足够的内容以包括切片和偏移量 */
		if (fc_slicelimit >= 0)
			fc_preslice = fc_toast_decompress_datum_slice(fc_tmp, fc_slicelimit);
		else
			fc_preslice = fc_toast_decompress_datum(fc_tmp);

		if (fc_tmp != fc_attr)
			pfree(fc_tmp);
	}

	if (VARATT_IS_SHORT(fc_preslice))
	{
		fc_attrdata = VARDATA_SHORT(fc_preslice);
		fc_attrsize = VARSIZE_SHORT(fc_preslice) - VARHDRSZ_SHORT;
	}
	else
	{
		fc_attrdata = VARDATA(fc_preslice);
		fc_attrsize = VARSIZE(fc_preslice) - VARHDRSZ;
	}

	/* 针对压缩情况和普通值的datum切片 */

	if (fc_sliceoffset >= fc_attrsize)
	{
		fc_sliceoffset = 0;
		fc_slicelength = 0;
	}
	else if (fc_slicelength < 0 || fc_slicelimit > fc_attrsize)
		fc_slicelength = fc_attrsize - fc_sliceoffset;

	fc_result = (struct varlena *) palloc(fc_slicelength + VARHDRSZ);
	SET_VARSIZE(fc_result, fc_slicelength + VARHDRSZ);

	memcpy(VARDATA(fc_result), fc_attrdata + fc_sliceoffset, fc_slicelength);

	if (fc_preslice != fc_attr)
		pfree(fc_preslice);

	return fc_result;
}

/* ----------
 * toast_fetch_datum -
 *
 * 从保存的toast关系中的块重建内存中的Datum
 * ----------
 */
static struct varlena * fc_toast_fetch_datum(struct varlena *fc_attr)
{
	Relation	fc_toastrel;
	struct varlena *fc_result;
	struct varatt_external fc_toast_pointer;
	int32		fc_attrsize;

	if (!VARATT_IS_EXTERNAL_ONDISK(fc_attr))
		elog(ERROR, "toast_fetch_datum shouldn't be called for non-ondisk datums");

	/* 必须复制以访问对齐字段 */
	VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

	fc_attrsize = VARATT_EXTERNAL_GET_EXTSIZE(fc_toast_pointer);

	fc_result = (struct varlena *) palloc(fc_attrsize + VARHDRSZ);

	if (VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer))
		SET_VARSIZE_COMPRESSED(fc_result, fc_attrsize + VARHDRSZ);
	else
		SET_VARSIZE(fc_result, fc_attrsize + VARHDRSZ);

	if (fc_attrsize == 0)
		return fc_result;			/* 可能不应该发生，但以防万一。 */

	/*
	 * 打开toast关系及其索引
	 */
	fc_toastrel = table_open(fc_toast_pointer.va_toastrelid, AccessShareLock);

	/* 提取所有块 */
	table_relation_fetch_toast_slice(fc_toastrel, fc_toast_pointer.va_valueid,
									 fc_attrsize, 0, fc_attrsize, fc_result);

	/* 关闭toast表 */
	table_close(fc_toastrel, AccessShareLock);

	return fc_result;
}

/* ----------
 * toast_fetch_datum_slice -
 *
 * 从保存的toast关系中的块重建Datum的一个片段
 *
 * 请注意，此函数支持非压缩外部datum
 * 和压缩外部datum（在这种情况下，请求的切片
 * 必须是前缀，即sliceoffset必须为0）。
 * ----------
 */
static struct varlena * fc_toast_fetch_datum_slice(struct varlena *fc_attr, int32 fc_sliceoffset,
						int32 fc_slicelength)
{
	Relation	fc_toastrel;
	struct varlena *fc_result;
	struct varatt_external fc_toast_pointer;
	int32		fc_attrsize;

	if (!VARATT_IS_EXTERNAL_ONDISK(fc_attr))
		elog(ERROR, "toast_fetch_datum_slice shouldn't be called for non-ondisk datums");

	/* 必须复制以访问对齐字段 */
	VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

	/*
	 * 除非在它是前缀时，从压缩datum提取切片是无意义的——
	 * 这不是lo_*，我们不能返回一个对后续toast有意义的
	 * 压缩datum。
	 */
	Assert(!VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer) || 0 == fc_sliceoffset);

	fc_attrsize = VARATT_EXTERNAL_GET_EXTSIZE(fc_toast_pointer);

	if (fc_sliceoffset >= fc_attrsize)
	{
		fc_sliceoffset = 0;
		fc_slicelength = 0;
	}

	/*
	 * 在提取压缩外部datum的前缀时，考虑到va_tcinfo所需的
	 * 空间，它作为一个int32值存储在开头。
	 */
	if (VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer) && fc_slicelength > 0)
		fc_slicelength = fc_slicelength + sizeof(int32);

	/*
	 * 如果需要，调整长度请求。 （注意：我们唯一的调用者，
	 * detoast_attr_slice，保护我们免受sliceoffset + slicelength
	 * 溢出。）
	 */
	if (((fc_sliceoffset + fc_slicelength) > fc_attrsize) || fc_slicelength < 0)
		fc_slicelength = fc_attrsize - fc_sliceoffset;

	fc_result = (struct varlena *) palloc(fc_slicelength + VARHDRSZ);

	if (VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer))
		SET_VARSIZE_COMPRESSED(fc_result, fc_slicelength + VARHDRSZ);
	else
		SET_VARSIZE(fc_result, fc_slicelength + VARHDRSZ);

	if (fc_slicelength == 0)
		return fc_result;			/* 此时可以节省大量工作！ */

	/* 打开toast关系 */
	fc_toastrel = table_open(fc_toast_pointer.va_toastrelid, AccessShareLock);

	/* 提取所有块 */
	table_relation_fetch_toast_slice(fc_toastrel, fc_toast_pointer.va_valueid,
									 fc_attrsize, fc_sliceoffset, fc_slicelength,
									 fc_result);

	/* 关闭toast表 */
	table_close(fc_toastrel, AccessShareLock);

	return fc_result;
}

/* ----------
 * toast_decompress_datum -
 *
 * 解压缩varlena datum的压缩版本
 */
static struct varlena * fc_toast_decompress_datum(struct varlena *fc_attr)
{
	ToastCompressionId fc_cmid;

	Assert(VARATT_IS_COMPRESSED(fc_attr));

	/*
	 * 提取存储在压缩头中的压缩方法ID，并使用相应的解压缩
	 * 例程解压缩数据。
	 */
	fc_cmid = TOAST_COMPRESS_METHOD(fc_attr);
	switch (fc_cmid)
	{
		case TOAST_PGLZ_COMPRESSION_ID:
			return pglz_decompress_datum(fc_attr);
		case TOAST_LZ4_COMPRESSION_ID:
			return lz4_decompress_datum(fc_attr);
		default:
			elog(ERROR, "invalid compression method id %d", fc_cmid);
			return NULL;		/* 保持编译器安静 */
	}
}


/* ----------
 * toast_decompress_datum_slice -
 *
 * 解压缩varlena datum压缩版本的前部分。
 * 偏移量处理发生在detoast_attr_slice中。
 * 在这里我们只从前面解压缩一个切片。
 */
static struct varlena * fc_toast_decompress_datum_slice(struct varlena *fc_attr, int32 fc_slicelength)
{
	ToastCompressionId fc_cmid;

	Assert(VARATT_IS_COMPRESSED(fc_attr));

	/*
	 * 一些调用者可能会传递一个比实际解压缩大小更大的切片长度。
	 * 如果是这样，就正常解压缩。这可以避免可能分配过大的结果对象，并且可能更快和/或更稳健。
	 * 值得注意的是，某些版本的liblz4在传递一个比数据的真实解压缩大小更大的输出大小时，
	 * 已经被观察到会给出错误的结果。
	 */
	if ((uint32) fc_slicelength >= TOAST_COMPRESS_EXTSIZE(fc_attr))
		return fc_toast_decompress_datum(fc_attr);

	/*
	 * 获取存储在压缩头中的压缩方法ID，并使用适当的解压缩例程解压数据切片。
	 */
	fc_cmid = TOAST_COMPRESS_METHOD(fc_attr);
	switch (fc_cmid)
	{
		case TOAST_PGLZ_COMPRESSION_ID:
			return pglz_decompress_datum_slice(fc_attr, fc_slicelength);
		case TOAST_LZ4_COMPRESSION_ID:
			return lz4_decompress_datum_slice(fc_attr, fc_slicelength);
		default:
			elog(ERROR, "invalid compression method id %d", fc_cmid);
			return NULL;		/* 保持编译器安静 */
	}
}

/* ----------
 * toast_raw_datum_size -
 *
 *	返回varlena数据的原始（未烤制）大小
 *	（包括VARHDRSZ头部）
 * ----------
 */
Size toast_raw_datum_size(Datum fc_value)
{
	struct varlena *fc_attr = (struct varlena *) DatumGetPointer(fc_value);
	Size		fc_result;

	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		/* va_rawsize是原始数据的大小--包括头部 */
		struct varatt_external fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);
		fc_result = fc_toast_pointer.va_rawsize;
	}
	else if (VARATT_IS_EXTERNAL_INDIRECT(fc_attr))
	{
		struct varatt_indirect fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

		/* 不允许嵌套间接Datum */
		Assert(!VARATT_IS_EXTERNAL_INDIRECT(fc_toast_pointer.pointer));

		return toast_raw_datum_size(PointerGetDatum(fc_toast_pointer.pointer));
	}
	else if (VARATT_IS_EXTERNAL_EXPANDED(fc_attr))
	{
		fc_result = EOH_get_flat_size(DatumGetEOHP(fc_value));
	}
	else if (VARATT_IS_COMPRESSED(fc_attr))
	{
		/* 在这里，va_rawsize仅为有效载荷大小 */
		fc_result = VARDATA_COMPRESSED_GET_EXTSIZE(fc_attr) + VARHDRSZ;
	}
	else if (VARATT_IS_SHORT(fc_attr))
	{
		/*
		 * 我们必须将头部长度归一化为VARHDRSZ，否则这个函数的调用者会感到困惑。
		 */
		fc_result = VARSIZE_SHORT(fc_attr) - VARHDRSZ_SHORT + VARHDRSZ;
	}
	else
	{
		/* 普通未烤制数据 */
		fc_result = VARSIZE(fc_attr);
	}
	return fc_result;
}

/* ----------
 * toast_datum_size
 *
 *	返回varlena数据的物理存储大小（可能已压缩）
 * ----------
 */
Size toast_datum_size(Datum fc_value)
{
	struct varlena *fc_attr = (struct varlena *) DatumGetPointer(fc_value);
	Size		fc_result;

	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		/*
		 * 属性存储在外部 - 无论是否压缩都返回外部大小。
		 * 我们不计算toast指针的大小...我们应该这样做吗？
		 */
		struct varatt_external fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);
		fc_result = VARATT_EXTERNAL_GET_EXTSIZE(fc_toast_pointer);
	}
	else if (VARATT_IS_EXTERNAL_INDIRECT(fc_attr))
	{
		struct varatt_indirect fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

		/* 不允许嵌套间接Datum */
		Assert(!VARATT_IS_EXTERNAL_INDIRECT(fc_attr));

		return toast_datum_size(PointerGetDatum(fc_toast_pointer.pointer));
	}
	else if (VARATT_IS_EXTERNAL_EXPANDED(fc_attr))
	{
		fc_result = EOH_get_flat_size(DatumGetEOHP(fc_value));
	}
	else if (VARATT_IS_SHORT(fc_attr))
	{
		fc_result = VARSIZE_SHORT(fc_attr);
	}
	else
	{
		/*
		 * 属性以行内方式存储，无论是否压缩，都计算数据的大小。
		 */
		fc_result = VARSIZE(fc_attr);
	}
	return fc_result;
}
